package xyz.yq56.easytool.provider.jwt;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import lombok.extern.slf4j.Slf4j;
import xyz.yq56.easytool.enums.JwtClaimKey;
import xyz.yq56.easytool.properties.EasyToolProperties;
import xyz.yq56.easytool.utils.collection.MapUtil;
import xyz.yq56.easytool.utils.date.DateUtils;
import xyz.yq56.easytool.utils.reflect.ReflectUtil;

/**
 * Jwt工具类
 *
 * @author yi qiang
 * @date 2021/10/1 17:55
 */
@Slf4j
public class JwtProvider implements InitializingBean {

    @Autowired(required = false)
    private EasyToolProperties easyToolProperties;

    //************************* 生成JWT *************************************************************//

    /**
     * 生成认证token
     *
     * @param claims   私有声明
     * @param username 用户名
     * @return 认证token
     */
    public String generateAccessToken(Map<String, Object> claims, String username) {
        return generateToken(claims, username, getJwtProp().getAccessKey(), getJwtProp().getAccessExpiry());
    }

    /**
     * 生成刷新token
     *
     * @param username 用户名
     * @return 刷新token
     */
    public String generateRefreshToken(Map<String, Object> claims, String username) {
        return generateToken(claims, username, getJwtProp().getRefreshKey(), getJwtProp().getRefreshExpiry());
    }

    /**
     * 从fresh token生成access token
     *
     * @param token fresh token
     * @return token
     */
    public String generateAccessTokenFromRefresh(String token) {
        Map<String, Object> claims = validateRefreshToken(token);
        return generateToken(claims, String.valueOf(claims.get(JwtClaimKey.SUB.getKey())), getJwtProp().getAccessKey(), getJwtProp().getAccessExpiry());
    }


    /**
     * 生成令牌环
     */
    public String generateToken(Map<String, Object> claims, String username, String key, long expiry) {
        String jwt = Jwts.builder()
                .setClaims(claims)
                .setExpiration(DateUtils.addDate(expiry))
                .setIssuedAt(new Date())
                .setSubject(username)
                .signWith(SignatureAlgorithm.HS512, key)
                .compact();
        return getJwtProp().getPrefix() + " " + jwt;
    }

    /**
     * 构建Claims
     *
     * @param uid         用户id
     * @param roles       用户角色
     * @param authorities 权限
     * @return 私有声明
     */
    public static Map<String, Object> buildClaims(String uid, String roles, String authorities) {
        return MapUtil.builder()
                .putIfValueNotNull(JwtClaimKey.UID.getKey(), uid)
                .putIfValueNotNull(JwtClaimKey.ROLES.getKey(), roles)
                .putIfValueNotNull(JwtClaimKey.AUTHORITIES.getKey(), authorities)
                .maps();
    }

    //************************* 验证JWT *************************************************************//

    /**
     * 校验access token
     *
     * @param token access token
     * @return 私有声明
     */
    public Map<String, Object> validateAccessToken(String token) {
        return validateTokenAndGetClaims(token, getJwtProp().getAccessKey());
    }

    /**
     * 校验refresh token
     *
     * @param token refresh token
     * @return 私有声明
     */
    public Map<String, Object> validateRefreshToken(String token) {
        return validateTokenAndGetClaims(token, getJwtProp().getRefreshKey());
    }

    /**
     * 令牌环校验
     *
     * @param token token
     * @return 验证结果
     */
    public Map<String, Object> validateTokenAndGetClaims(String token, String key) {
        return Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token.replace(getJwtProp().getPrefix(), ""))
                .getBody();
    }

    @Override
    public void afterPropertiesSet() {
        Class<EasyToolProperties.Jwt> jwtClass = EasyToolProperties.Jwt.class;
        Field[] fields = jwtClass.getDeclaredFields();
        for (Field field : fields) {
            Method method = ReflectUtil.fieldGetMethod(jwtClass, field.getName());
            if (method != null) {
                log.info(field.getName() + ": " + ReflectUtil.invokeMethod(method, getJwtProp()));
            }

        }
    }

    private EasyToolProperties.Jwt getJwtProp() {
        return easyToolProperties.getJwt();
    }

}
